Stack Overflow - eksploracyjna analiza danych

Kalkulacje oceny

Wskaźniki oceny kandydata

Poniżej przedstawione są kalkulacje poszczególnych wskaźników oceny kandydata (zaproponowanych przez Beniamina), na przykładzie jednego użytkownika.


1. Count all answers in a tag (skill)

INPUT: Users.Id

RESULT: User’s number of answers per Tags.CalculatedTagGroup which he had given any answer

"SELECT u.DisplayName, t.CalculatedTagGroup, COUNT(*) AS NumberOfAnswersPerCalculatedTagGroup
FROM Posts p
JOIN PostTags pt ON p.ParentId = pt.PostId
JOIN Tags t ON t.TagName = pt.TagName
JOIN Users u ON u.Id = p.OwnerUserId
WHERE u.Id = 422489 AND p.PostTypeId = 2
GROUP BY u.DisplayName, t.CalculatedTagGroup
ORDER BY u.DisplayName, NumberOfAnswersPerCalculatedTagGroup DESC" -> sql1
dbGetQuery(db, sql1)

2. Calculate percentage of “accepted” answers out of all answers per tag (skill)

INPUT: Users.Id

RESULT: User’s Percentage of accepted answers

percentage of “accepted” answers = NumerOfAcceptedAnswersPerCalculatedTagGroup/NumberOfAnswersPerCalculatedTagGroup (from point 1)

"SELECT u.Id, u.DisplayName, t.CalculatedTagGroup, COUNT(*) AS NumberOfAcceptedAnswersPerCalculatedTagGroup
FROM Posts pa
JOIN Posts pq ON pq.Id = pa.ParentId
JOIN PostTags pt ON pt.PostId = pa.ParentId
JOIN Tags t ON (t.Tagname = pt.Tagname)
JOIN Users u ON u.Id = pa.OwnerUserId
WHERE pq.AcceptedAnswerId = pa.Id AND pa.PostTypeId = 2 AND u.Id = 422489
GROUP BY u.Id, u.DisplayName, t.CalculatedTagGroup
ORDER BY u.DisplayName, NumberOfAcceptedAnswersPerCalculatedTagGroup DESC" -> sql2
dbGetQuery(db, sql2)

3. Calculate avarage score from answers per tag (skill)

INPUT: Users.Id

RESULT: User’s average scores of answers per Tags.CalculatedTagGroup

average scores of answers per Tags.CalculatedTagGroup = SumOfScorePerTag/NumberOfAnswersPerCalculatedTagGroup

"SELECT u.DisplayName, t.CalculatedTagGroup, SUM(p.Score) AS SumOfScorePerTag, SUM(p.Score) / COUNT(*) AS AvarageSumOfScorePerTag
FROM Posts p
JOIN PostTags pt ON p.ParentId = pt.PostId
JOIN Tags t ON t.TagName = pt.TagName
JOIN Users u ON u.Id = p.OwnerUserId
WHERE u.Id = 422489 AND p.PostTypeId = 2
GROUP BY u.DisplayName, t.CalculatedTagGroup
ORDER BY u.DisplayName, AvarageSumOfScorePerTag DESC" -> sql3
dbGetQuery(db, sql3)

4. Calculate median of percentage of candidates answers out of total answers under a post

INPUT: Users.Id

RESULT: User’s median of percentage answers per question

median of percentage of candidates answers out of total answers under a post = NumberOfUserAnswers / NumberOfPostAnswers

a) find all Tags.CalculatedTagGroup which User.Id has written any answer
"SELECT u.Id, u.DisplayName, t.CalculatedTagGroup
FROM Posts p
JOIN PostTags pt ON p.ParentId = pt.PostId
JOIN Tags t ON t.TagName = pt.TagName
JOIN Users u ON u.Id = p.OwnerUserId
WHERE u.Id = 422489 AND p.PostTypeId = 2
GROUP BY u.DisplayName, t.CalculatedTagGroup
ORDER BY u.DisplayName, t.CalculatedTagGroup" -> sql4a
dbGetQuery(db, sql4a)
b) for each Tags.CalculatedTagGroup from a) we have to calculate percentage of User.Id answers out of total answers under a post teh tocalculate mediana of this percentage.
"SELECT u.Id, u.DisplayName, pa.Id, t.CalculatedTagGroup, SUM(pq.AnswerCount) AS NumberOfPostAnswers, COUNT(*) AS NumberOfUserAnswers, COUNT(*) / SUM(pq.AnswerCount)
FROM Posts pa
JOIN Posts pq ON pq.Id = pa.ParentId
JOIN PostTags pt ON pt.PostId = pa.ParentId
JOIN Tags t ON (t.Tagname = pt.Tagname)
JOIN Users u ON u.Id = pa.OwnerUserId
WHERE pa.PostTypeId = 2 AND u.Id = 422489 AND t.CalculatedTagGroup = 'c++'
GROUP BY u.Id, u.DisplayName, t.CalculatedTagGroup, pa.Id
ORDER BY u.DisplayName, t.CalculatedTagGroup" -> sql4b
dbGetQuery(db, sql4b)

Analiza userów

Reputacja

Pełna historia od 2008

Poniżej zaprezentowane są segmenty użytkowników w podziale na ich punkty reputacji. 1 punkt nadawany jest automatycznie dla każdego konta, czyli są to konta bez punktowanej aktywności. Wydaje się, że za aktywnych użytkowników - takich, że ich aktywność jest wystarczająca by móc ich ocenić - należy uznać użytkowników z minimum 100 punktami reputacji. 100 punktów oznacza [w przybliżeniu] mniej niż 5 postów i/lub mniej niż 5 otrzymanych vote’ów.

Metodę liczenia punktów reputacji można przeczytać tutaj: What is Reputation?.

You gain reputation when:

  • question is voted up: +10
  • answer is voted up: +10
  • answer is marked “accepted”: +15 (+2 to acceptor)
  • suggested edit is accepted: +2 (up to +1000 total per user)
  • bounty awarded to your answer: + full bounty amount
  • one of your answers is awarded a bounty automatically: + half of the bounty amount (see more details about how bounties work)
  • site association bonus: +100 on each site (awarded a maximum of one time per site)

Analiza postów

Segmenty użytkowników

Pełna historia od 2008

Poniżej zaprezentowane są segmenty użytkowników w podziale na liczbę udzielonych pytań i odpowiedzi. Wydaje się, że za aktywnych użytkowników - takich, że ich aktywność jest wystarczająca by móc ich ocenić - należy uznać użytkowników z minimum 5 postami, w szczególności jeśli mając mniej niż 5 postów, nie otrzymano żadnych vote’ów.

Legenda:

Etykieta Opis
(-1,0] 0 postów
(0,1] 1 post
(1,5] 2-5 postów
(5, 1e+05] powyżej 5 postów

Świeże posty

Poniżej zaprezentowane są segmenty użytkowników biorąc pod uwagę tylko posty z ostatnich 3 lat.


Analiza odpowiedzi

Liczba odpowiedzi użytkowników - pełna historia od 2008

Większość użytkowników (66.43%) nigdy nie napisała żadnej odpowiedzi. Kolejne 9.71% napisało tylko 1 odpowiedź, 9.82% napisało pomiędzy 1 a 5 odpowiedzi. Kolejne przedziały liczby odpowiedzi są zaprezentowane na poniższym wykresie.


Liczba odpowiedzi w czasie

Odpowiedzi istniejące w bazie pochodzą z okresu 2008-08-05 - 2019-12-29. Widać, że lata 2008 - 2013 były czasem wzrostu wolumenu odpowiedzi, podczas gdy od 2013 miesięczna liczba odpowiedzi ustabilizowała się. Prezentuje to poniższy wykres.


Liczba świeżych odpowiedzi użytkowników

Aby uzyskać aktualną informację o zaangażowaniu użytkownika na platformie warto ograniczyć analizę tylko do wpisów np. z ostatnich 3 lat.

Ograniczając odpowiedzi tylko do ostatnich 3 lat widać, że ogólny poziom zaangażowania jest niższy niż analizując całą bazę danych. Większość użytkowników (77.21%) nigdy nie napisała żadnej odpowiedzi. Kolejne 9.02% napisało tylko 1 odpowiedź, 7.63% napisało pomiędzy 1 a 5 odpowiedzi. Kolejne przedziały liczby odpowiedzi są zaprezentowane na poniższym wykresie.


Analiza pytań

Liczba pytań użytkowników

Istotnie większa część użytkowników zapostowała co najmniej jedno pytanie niż odpowiedź - 41.43% vs 33.57%

Większość użytkowników (58.57%) nigdy nie napisała żadnego pytania. Kolejne 16.1% napisało tylko 1 pytanie, 14.81% napisało pomiędzy 1 a 5 pytaniami. Kolejne przedziały liczby pytań są zaprezentowane na poniższym wykresie.


Liczba pytań w czasie

Pytania istniejące w bazie pochodzą z okresu 2008-08-05 - 2019-12-29. Tu, w odróżnieniu od odpowiedzi, mamy do czynienia z trendem stale rosnącym. Może to oznaczać, że popyt na wiedzę wciąż rośnie, jednak podaż odpowiadających się ustabilizowała kilka lat temu i nie przyrasta proporcjonalnie do popytu.

Liczba świeżych pytań użytkowników

Aby uzyskać aktualną informację o zaangażowaniu użytkownika na platformie warto ograniczyć analizę tylko do wpisów np. z ostatnich 3 lat.

Ograniczając pytania tylko do ostatnich 3 lat widać, że ogólny poziom zaangażowania jest niższy niż analizując całą bazę danych. Większość użytkowników (69.89%) nigdy nie napisała żadnego pytania. Kolejne 14.82% napisało tylko 1 pytanie, 10.81% napisało pomiędzy 1 a 5 pytaniami. Kolejne przedziały liczby pytań są zaprezentowane na poniższym wykresie.


Pozostałe typy postów

W Stackoverflow jest zdefiniowane kilka typów postów, poza najpopularniejszymi czyli pytaniami i odpowiedziami. Jednak inne typy postów niż te analizowane powyżej są zaniedbywalnie rzadkie, więc pomijamy je w analizie. Ich rozkład jest zaprezentowany poniżej.

left_join(posts, posttypes, by = c("PostTypeId" = "Id")) %>% count(PostTypeName = Name) %>% arrange(desc(n))

Analiza tagów

Liczebności tagów

W naszej bazie postów występuje 27423 unikalnych tagów. Rozkład ćwiartkowy występowania wszystkich tagów wygląda następująco:

  • 25% wszystkich występujących tagów to 11 unikalnych tagów, a dokładnie: c(“java”, “javascript”, “c#”, “php”, “python”, “android”, “c++”, “jquery”, “regex”, “html”, “css”)
  • Kolejne 25% wszystkich tagów to 92 unikalnych tagów
  • Kolejne 25% wszystkich tagów to 687 unikalnych tagów
  • Ostatnie 25% wszystkich tagów to 26633 unikalnych tagów

Chmura słów

Poniżej zaprezentowany jest rozkład najpopularniejszych tagów (występujących więcej niż 350 razy) zgodnie z częstością ich występowania w postach. Im większy rozmiar słowa, tym częściej występuje.

Jak widać, najczęstsze tagi oznaczają języki programowania (java, python, c#) lub popularne elementy programistyczne (regex, arrays, json)


Supertagi

W celu ułatwienia interpretacji tagów, których jest aż 27423 wykonaliśmy ich grupowanie do “supertagów”. Metoda grupowania wykorzystuje fakt, że do większości postów przypisane są co najmniej 2 tagi. Sprawdzamy, który z nich jest popularniejszy ogółem i dla każdego posta przypisujemy ten najpopularniejszy jako supertag do wszystkich pozostałych tagów. Jeżeli w większości postów z naszej bazy danych dany tag jest przypisany do danego supertaga, to uznajemy te grupowanie za zasadne. Jeśli jednak występują różne supertagi i żaden nie stanowi większości, to nie przypisujemy żadnego supertaga.

Poniższa chmura prezentuje wszystkie supertagi, które wytworzyliśmy, 800 sztuk. Pokrywają one ca. 85% wystąpień wszystkich tagów w postach.

Opis bazy danych

Wszystkie tabele

dbListTables(db)
[1] "badges"    "posts"     "posttags"  "posttypes" "supertags" "tags"      "users"    

Lista tabel w bazie stackoverflow @ itm-innovation-mysql.mysql.database.azure.com:3306

Tabele nadające się do wykorzystania to:

  • Badges - informacje o Badge’ach użytkowników
  • PostTags - tagi przypisane do postów
  • Posts - lista pytań i odpowiedzi
  • Users - informacje na temat użytkowników

Wszystkie 4 tabele są zaprezentowane poniżej.


Tabela Badges

colnames(badges)
[1] "Id"       "UserId"   "Name"     "Date"     "Class"    "TagBased"

Lista kolumn w tabeli Badges (informacje o Badge’ach użytkowników). Tabela zawiera 264291 rekordów i 560 unikalnych Badges.Name.

head(badges, 10)

Prezentacja danych w tabeli Badges


Tabela PostTags

colnames(post_tags)
[1] "PostId"  "TagName"

Lista kolumn w tabeli PostTags (tagi przypisane do postów). Tabela zawiera 1184864 rekordów, w tym 27423 unikalnych PostTags.TagName.

head(post_tags, 10)

Prezentacja danych w tabeli PostTags


Tabela Posts

colnames(posts)
 [1] "Id"                    "PostTypeId"            "AcceptedAnswerId"      "ParentId"              "CreationDate"         
 [6] "DeletionDate"          "Score"                 "ViewCount"             "Body"                  "OwnerUserId"          
[11] "OwnerDisplayName"      "LastEditorUserId"      "LastEditorDisplayName" "LastEditDate"          "LastActivityDate"     
[16] "Title"                 "Tags"                  "AnswerCount"           "CommentCount"          "FavoriteCount"        
[21] "ClosedDate"            "CommunityOwnedDate"   

Lista kolumn w tabeli Posts (lista pytań i odpowiedzi). Tabela zawiera 737178 rekordów, w tym 316789 pytań i 420389 odpowiedzi.

head(posts, 10)

Prezentacja danych w tabeli Posts


Tabela Users

colnames(users)
 [1] "Id"              "Reputation"      "CreationDate"    "DisplayName"     "LastAccessDate"  "WebsiteUrl"      "Location"       
 [8] "AboutMe"         "Views"           "UpVotes"         "DownVotes"       "ProfileImageUrl" "EmailHash"       "AccountId"      

Lista kolumn w tabeli Users (informacje na temat użytkowników). Tabela zawiera 41391 rekordów, w tym 41391 unikalnych User ID.

head(users, 10)

Prezentacja danych w tabeli Users

LS0tDQp0aXRsZTogIklubm9CZW5jaCAvIERhdGEgLyBBZHZhbmNlZCBTb3VyY2luZyINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KZGF0ZTogJzIwMjAtMDEtMjAnDQotLS0NCg0KIyBTdGFjayBPdmVyZmxvdyAtIGVrc3Bsb3JhY3lqbmEgYW5hbGl6YSBkYW55Y2ggIHsudGFic2V0fQ0KDQpgYGB7ciByZXN1bHRzID0gJ2hpZGUnLCBlY2hvID0gRiwgZXJyb3IgPSBGLCB3YXJuaW5nID0gRiwgbWVzc2FnZSA9IEZ9DQpsaWJyYXJ5KFJNeVNRTCkNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KHBsb3RseSkNCmxpYnJhcnkobHVicmlkYXRlKQ0KbGlicmFyeSh3b3JkY2xvdWQpDQpsaWJyYXJ5KGRhdGEudGFibGUpDQoNCmRiIDwtIGRiQ29ubmVjdChkYkRyaXZlcignTXlTUUwnKSwgdXNlciA9ICdqYXJvc2xhdy5ib250cnVrQGl0bS1pbm5vdmF0aW9uLW15c3FsJywgcGFzc3dvcmQgPSAncTh5ckJ5NEUzVWF4Z1lndCcsIA0KICAgICAgICAgICAgICAgICBkYm5hbWUgPSAnc3RhY2tvdmVyZmxvdycsIGhvc3QgPSAnaXRtLWlubm92YXRpb24tbXlzcWwubXlzcWwuZGF0YWJhc2UuYXp1cmUuY29tJywgcG9ydCA9IDMzMDYpDQpiYWRnZXMgPC0gZGJSZWFkVGFibGUoZGIsICJiYWRnZXMiKQ0KdXNlcnMgPC0gZGJSZWFkVGFibGUoZGIsICJ1c2VycyIpDQp1c2VycyRJZCA8LSBhcy5udW1lcmljKHVzZXJzJElkKQ0KcG9zdHMgPC0gZGJSZWFkVGFibGUoZGIsICJwb3N0cyIpDQpwb3N0X3RhZ3MgPC0gZGJSZWFkVGFibGUoZGIsICJwb3N0dGFncyIpDQpzdXBlcnRhZ3MgPC0gZGJSZWFkVGFibGUoZGIsICJzdXBlcnRhZ3MiKQ0KcG9zdHR5cGVzIDwtIGRiUmVhZFRhYmxlKGRiLCAicG9zdHR5cGVzIikNCmBgYA0KDQojIyBLYWxrdWxhY2plIG9jZW55DQoNCiMjIyBXc2thxbpuaWtpIG9jZW55IGthbmR5ZGF0YQ0KDQpQb25pxbxlaiBwcnplZHN0YXdpb25lIHPEhSBrYWxrdWxhY2plIHBvc3pjemVnw7NsbnljaCB3c2thxbpuaWvDs3cgb2Nlbnkga2FuZHlkYXRhICh6YXByb3Bvbm93YW55Y2ggcHJ6ZXogQmVuaWFtaW5hKSwgbmEgcHJ6eWvFgmFkemllIGplZG5lZ28gdcW8eXRrb3duaWthLg0KDQoqKioNCg0KIyMjIyAxLiBDb3VudCBhbGwgYW5zd2VycyBpbiBhIHRhZyAoc2tpbGwpDQpJTlBVVDogIFVzZXJzLklkDQoNClJFU1VMVDogVXNlcuKAmXMgbnVtYmVyIG9mIGFuc3dlcnMgcGVyIFRhZ3MuQ2FsY3VsYXRlZFRhZ0dyb3VwIHdoaWNoIGhlIGhhZCBnaXZlbiBhbnkgYW5zd2VyDQoNCmBgYHtyIGVycm9yID0gRiwgd2FybmluZyA9IEYsIG1lc3NhZ2UgPSBGfQ0KIlNFTEVDVCB1LkRpc3BsYXlOYW1lLCB0LkNhbGN1bGF0ZWRUYWdHcm91cCwgQ09VTlQoKikgQVMgTnVtYmVyT2ZBbnN3ZXJzUGVyQ2FsY3VsYXRlZFRhZ0dyb3VwDQpGUk9NIFBvc3RzIHANCkpPSU4gUG9zdFRhZ3MgcHQgT04gcC5QYXJlbnRJZCA9IHB0LlBvc3RJZA0KSk9JTiBUYWdzIHQgT04gdC5UYWdOYW1lID0gcHQuVGFnTmFtZQ0KSk9JTiBVc2VycyB1IE9OIHUuSWQgPSBwLk93bmVyVXNlcklkDQpXSEVSRSB1LklkID0gNDIyNDg5IEFORCBwLlBvc3RUeXBlSWQgPSAyDQpHUk9VUCBCWSB1LkRpc3BsYXlOYW1lLCB0LkNhbGN1bGF0ZWRUYWdHcm91cA0KT1JERVIgQlkgdS5EaXNwbGF5TmFtZSwgTnVtYmVyT2ZBbnN3ZXJzUGVyQ2FsY3VsYXRlZFRhZ0dyb3VwIERFU0MiIC0+IHNxbDENCmRiR2V0UXVlcnkoZGIsIHNxbDEpDQpgYGANCg0KKioqDQoNCiMjIyMgMi4gQ2FsY3VsYXRlIHBlcmNlbnRhZ2Ugb2YgImFjY2VwdGVkIiBhbnN3ZXJzIG91dCBvZiBhbGwgYW5zd2VycyBwZXIgdGFnIChza2lsbCkNCklOUFVUOiAgVXNlcnMuSWQNCg0KUkVTVUxUOiBVc2Vy4oCZcyBQZXJjZW50YWdlIG9mIGFjY2VwdGVkIGFuc3dlcnMNCg0KcGVyY2VudGFnZSBvZiAiYWNjZXB0ZWQiIGFuc3dlcnMgPSBOdW1lck9mQWNjZXB0ZWRBbnN3ZXJzUGVyQ2FsY3VsYXRlZFRhZ0dyb3VwL051bWJlck9mQW5zd2Vyc1BlckNhbGN1bGF0ZWRUYWdHcm91cCAoZnJvbSBwb2ludCAxKQ0KDQpgYGB7ciBlcnJvciA9IEYsIHdhcm5pbmcgPSBGLCBtZXNzYWdlID0gRn0NCiJTRUxFQ1QgdS5JZCwgdS5EaXNwbGF5TmFtZSwgdC5DYWxjdWxhdGVkVGFnR3JvdXAsIENPVU5UKCopIEFTIE51bWJlck9mQWNjZXB0ZWRBbnN3ZXJzUGVyQ2FsY3VsYXRlZFRhZ0dyb3VwDQpGUk9NIFBvc3RzIHBhDQpKT0lOIFBvc3RzIHBxIE9OIHBxLklkID0gcGEuUGFyZW50SWQNCkpPSU4gUG9zdFRhZ3MgcHQgT04gcHQuUG9zdElkID0gcGEuUGFyZW50SWQNCkpPSU4gVGFncyB0IE9OICh0LlRhZ25hbWUgPSBwdC5UYWduYW1lKQ0KSk9JTiBVc2VycyB1IE9OIHUuSWQgPSBwYS5Pd25lclVzZXJJZA0KV0hFUkUgcHEuQWNjZXB0ZWRBbnN3ZXJJZCA9IHBhLklkIEFORCBwYS5Qb3N0VHlwZUlkID0gMiBBTkQgdS5JZCA9IDQyMjQ4OQ0KR1JPVVAgQlkgdS5JZCwgdS5EaXNwbGF5TmFtZSwgdC5DYWxjdWxhdGVkVGFnR3JvdXANCk9SREVSIEJZIHUuRGlzcGxheU5hbWUsIE51bWJlck9mQWNjZXB0ZWRBbnN3ZXJzUGVyQ2FsY3VsYXRlZFRhZ0dyb3VwIERFU0MiIC0+IHNxbDINCmRiR2V0UXVlcnkoZGIsIHNxbDIpDQpgYGANCg0KKioqDQoNCiMjIyMgMy4gQ2FsY3VsYXRlIGF2YXJhZ2Ugc2NvcmUgZnJvbSBhbnN3ZXJzIHBlciB0YWcgKHNraWxsKQ0KSU5QVVQ6ICBVc2Vycy5JZA0KDQpSRVNVTFQ6IFVzZXLigJlzIGF2ZXJhZ2Ugc2NvcmVzIG9mIGFuc3dlcnMgcGVyIFRhZ3MuQ2FsY3VsYXRlZFRhZ0dyb3VwDQoNCmF2ZXJhZ2Ugc2NvcmVzIG9mIGFuc3dlcnMgcGVyIFRhZ3MuQ2FsY3VsYXRlZFRhZ0dyb3VwID0gU3VtT2ZTY29yZVBlclRhZy9OdW1iZXJPZkFuc3dlcnNQZXJDYWxjdWxhdGVkVGFnR3JvdXANCg0KYGBge3IgZXJyb3IgPSBGLCB3YXJuaW5nID0gRiwgbWVzc2FnZSA9IEZ9DQoiU0VMRUNUIHUuRGlzcGxheU5hbWUsIHQuQ2FsY3VsYXRlZFRhZ0dyb3VwLCBTVU0ocC5TY29yZSkgQVMgU3VtT2ZTY29yZVBlclRhZywgU1VNKHAuU2NvcmUpIC8gQ09VTlQoKikgQVMgQXZhcmFnZVN1bU9mU2NvcmVQZXJUYWcNCkZST00gUG9zdHMgcA0KSk9JTiBQb3N0VGFncyBwdCBPTiBwLlBhcmVudElkID0gcHQuUG9zdElkDQpKT0lOIFRhZ3MgdCBPTiB0LlRhZ05hbWUgPSBwdC5UYWdOYW1lDQpKT0lOIFVzZXJzIHUgT04gdS5JZCA9IHAuT3duZXJVc2VySWQNCldIRVJFIHUuSWQgPSA0MjI0ODkgQU5EIHAuUG9zdFR5cGVJZCA9IDINCkdST1VQIEJZIHUuRGlzcGxheU5hbWUsIHQuQ2FsY3VsYXRlZFRhZ0dyb3VwDQpPUkRFUiBCWSB1LkRpc3BsYXlOYW1lLCBBdmFyYWdlU3VtT2ZTY29yZVBlclRhZyBERVNDIiAtPiBzcWwzDQpkYkdldFF1ZXJ5KGRiLCBzcWwzKQ0KYGBgDQoNCioqKg0KDQojIyMjIDQuIENhbGN1bGF0ZSBtZWRpYW4gb2YgcGVyY2VudGFnZSBvZiBjYW5kaWRhdGVzIGFuc3dlcnMgb3V0IG9mIHRvdGFsIGFuc3dlcnMgdW5kZXIgYSBwb3N0DQpJTlBVVDogIFVzZXJzLklkDQoNClJFU1VMVDogVXNlcuKAmXMgbWVkaWFuIG9mIHBlcmNlbnRhZ2UgYW5zd2VycyBwZXIgcXVlc3Rpb24NCg0KbWVkaWFuIG9mIHBlcmNlbnRhZ2Ugb2YgY2FuZGlkYXRlcyBhbnN3ZXJzIG91dCBvZiB0b3RhbCBhbnN3ZXJzIHVuZGVyIGEgcG9zdCA9IE51bWJlck9mVXNlckFuc3dlcnMgLyBOdW1iZXJPZlBvc3RBbnN3ZXJzDQoNCiMjIyMjIGEpIGZpbmQgYWxsIFRhZ3MuQ2FsY3VsYXRlZFRhZ0dyb3VwIHdoaWNoIFVzZXIuSWQgaGFzIHdyaXR0ZW4gYW55IGFuc3dlcg0KDQpgYGB7ciBlcnJvciA9IEYsIHdhcm5pbmcgPSBGLCBtZXNzYWdlID0gRn0NCiJTRUxFQ1QgdS5JZCwgdS5EaXNwbGF5TmFtZSwgdC5DYWxjdWxhdGVkVGFnR3JvdXANCkZST00gUG9zdHMgcA0KSk9JTiBQb3N0VGFncyBwdCBPTiBwLlBhcmVudElkID0gcHQuUG9zdElkDQpKT0lOIFRhZ3MgdCBPTiB0LlRhZ05hbWUgPSBwdC5UYWdOYW1lDQpKT0lOIFVzZXJzIHUgT04gdS5JZCA9IHAuT3duZXJVc2VySWQNCldIRVJFIHUuSWQgPSA0MjI0ODkgQU5EIHAuUG9zdFR5cGVJZCA9IDINCkdST1VQIEJZIHUuRGlzcGxheU5hbWUsIHQuQ2FsY3VsYXRlZFRhZ0dyb3VwDQpPUkRFUiBCWSB1LkRpc3BsYXlOYW1lLCB0LkNhbGN1bGF0ZWRUYWdHcm91cCIgLT4gc3FsNGENCmRiR2V0UXVlcnkoZGIsIHNxbDRhKQ0KYGBgDQoNCiMjIyMjIGIpIGZvciBlYWNoIFRhZ3MuQ2FsY3VsYXRlZFRhZ0dyb3VwIGZyb20gYSkgd2UgaGF2ZSB0byBjYWxjdWxhdGUgcGVyY2VudGFnZSBvZiBVc2VyLklkIGFuc3dlcnMgb3V0IG9mIHRvdGFsIGFuc3dlcnMgdW5kZXIgYSBwb3N0IHRlaCB0b2NhbGN1bGF0ZSBtZWRpYW5hIG9mIHRoaXMgcGVyY2VudGFnZS4NCg0KYGBge3IgZXJyb3IgPSBGLCB3YXJuaW5nID0gRiwgbWVzc2FnZSA9IEZ9DQoiU0VMRUNUIHUuSWQsIHUuRGlzcGxheU5hbWUsIHBhLklkLCB0LkNhbGN1bGF0ZWRUYWdHcm91cCwgU1VNKHBxLkFuc3dlckNvdW50KSBBUyBOdW1iZXJPZlBvc3RBbnN3ZXJzLCBDT1VOVCgqKSBBUyBOdW1iZXJPZlVzZXJBbnN3ZXJzLCBDT1VOVCgqKSAvIFNVTShwcS5BbnN3ZXJDb3VudCkNCkZST00gUG9zdHMgcGENCkpPSU4gUG9zdHMgcHEgT04gcHEuSWQgPSBwYS5QYXJlbnRJZA0KSk9JTiBQb3N0VGFncyBwdCBPTiBwdC5Qb3N0SWQgPSBwYS5QYXJlbnRJZA0KSk9JTiBUYWdzIHQgT04gKHQuVGFnbmFtZSA9IHB0LlRhZ25hbWUpDQpKT0lOIFVzZXJzIHUgT04gdS5JZCA9IHBhLk93bmVyVXNlcklkDQpXSEVSRSBwYS5Qb3N0VHlwZUlkID0gMiBBTkQgdS5JZCA9IDQyMjQ4OSBBTkQgdC5DYWxjdWxhdGVkVGFnR3JvdXAgPSAnYysrJw0KR1JPVVAgQlkgdS5JZCwgdS5EaXNwbGF5TmFtZSwgdC5DYWxjdWxhdGVkVGFnR3JvdXAsIHBhLklkDQpPUkRFUiBCWSB1LkRpc3BsYXlOYW1lLCB0LkNhbGN1bGF0ZWRUYWdHcm91cCIgLT4gc3FsNGINCmRiR2V0UXVlcnkoZGIsIHNxbDRiKQ0KYGBgDQoNCg0KDQoNCiMjIEFuYWxpemEgdXNlcsOzdw0KDQpgYGB7ciBlY2hvID0gRiwgZXJyb3IgPSBGLCB3YXJuaW5nID0gRiwgbWVzc2FnZSA9IEZ9DQp1c2Vyc19yZXB1IDwtIHVzZXJzICU+JQ0KICBjb3VudChyZXB1ID0gYXMubnVtZXJpYyhSZXB1dGF0aW9uKSkgJT4lDQogIG11dGF0ZShiaW5uZWRfcmVwdSA9IGN1dChyZXB1LCBicmVha3MgPSBjKDAsIDEsIDI1LCA1MCwgMTAwLCAxMDAwMDAwMDApKSkgJT4lDQogIGdyb3VwX2J5KGJpbm5lZF9yZXB1KSAlPiUNCiAgc3VtbWFyaXNlKG4gPSBzdW0obiwgbmEucm0gPSBUKSkgJT4lDQogIG11dGF0ZShwZXJjZW50ID0gcm91bmQobi9zdW0obiksMikpDQpgYGANCg0KIyMjIFJlcHV0YWNqYQ0KIyMjIyBQZcWCbmEgaGlzdG9yaWEgb2QgMjAwOA0KUG9uacW8ZWogemFwcmV6ZW50b3dhbmUgc8SFIHNlZ21lbnR5IHXFvHl0a293bmlrw7N3IHcgcG9kemlhbGUgbmEgaWNoIHB1bmt0eSByZXB1dGFjamkuIDEgcHVua3QgbmFkYXdhbnkgamVzdCBhdXRvbWF0eWN6bmllIGRsYSBrYcW8ZGVnbyBrb250YSwgY3p5bGkgc8SFIHRvIGtvbnRhIGJleiBwdW5rdG93YW5laiBha3R5d25vxZtjaS4gV3lkYWplIHNpxJksIMW8ZSB6YSBha3R5d255Y2ggdcW8eXRrb3duaWvDs3cgLSB0YWtpY2gsIMW8ZSBpY2ggYWt0eXdub8WbxIcgamVzdCB3eXN0YXJjemFqxIVjYSBieSBtw7NjIGljaCBvY2VuacSHIC0gbmFsZcW8eSB1em5hxIcgdcW8eXRrb3duaWvDs3cgeiBtaW5pbXVtIDEwMCBwdW5rdGFtaSByZXB1dGFjamkuIDEwMCBwdW5rdMOzdyBvem5hY3phIFt3IHByenlibGnFvGVuaXVdIG1uaWVqIG5pxbwgNSBwb3N0w7N3IGkvbHViIG1uaWVqIG5pxbwgNSBvdHJ6eW1hbnljaCB2b3RlJ8Ozdy4NCg0KTWV0b2TEmSBsaWN6ZW5pYSBwdW5rdMOzdyByZXB1dGFjamkgbW/FvG5hIHByemVjenl0YcSHIHR1dGFqOiBbV2hhdCBpcyBSZXB1dGF0aW9uP10oaHR0cHM6Ly9zdGFja292ZXJmbG93LmNvbS9oZWxwL3doYXRzLXJlcHV0YXRpb24pLg0KDQpZb3UgZ2FpbiByZXB1dGF0aW9uIHdoZW46DQoNCiogcXVlc3Rpb24gaXMgdm90ZWQgdXA6ICsxMA0KKiBhbnN3ZXIgaXMgdm90ZWQgdXA6ICsxMA0KKiBhbnN3ZXIgaXMgbWFya2VkIOKAnGFjY2VwdGVk4oCdOiArMTUgKCsyIHRvIGFjY2VwdG9yKQ0KKiBzdWdnZXN0ZWQgZWRpdCBpcyBhY2NlcHRlZDogKzIgKHVwIHRvICsxMDAwIHRvdGFsIHBlciB1c2VyKQ0KKiBib3VudHkgYXdhcmRlZCB0byB5b3VyIGFuc3dlcjogKyBmdWxsIGJvdW50eSBhbW91bnQNCiogb25lIG9mIHlvdXIgYW5zd2VycyBpcyBhd2FyZGVkIGEgYm91bnR5IGF1dG9tYXRpY2FsbHk6ICsgaGFsZiBvZiB0aGUgYm91bnR5IGFtb3VudCAoc2VlIG1vcmUgZGV0YWlscyBhYm91dCBob3cgYm91bnRpZXMgd29yaykNCiogc2l0ZSBhc3NvY2lhdGlvbiBib251czogKzEwMCBvbiBlYWNoIHNpdGUgKGF3YXJkZWQgYSBtYXhpbXVtIG9mIG9uZSB0aW1lIHBlciBzaXRlKQ0KDQpgYGB7ciBlY2hvID0gRiwgZXJyb3IgPSBGLCB3YXJuaW5nID0gRiwgbWVzc2FnZSA9IEZ9DQpwbG90X2x5KHVzZXJzX3JlcHUsIGxhYmVscyA9IH5iaW5uZWRfcmVwdSwgdmFsdWVzID0gfm4sIHR5cGUgPSAncGllJywgc29ydCA9IEYpICU+JQ0KICBsYXlvdXQodGl0bGUgPSAnTGljemJhIHB1bmt0w7N3IHJlcHV0YWNqaScpDQpgYGANCg0KDQoNCg0KDQojIyBBbmFsaXphIHBvc3TDs3cNCg0KYGBge3IgZWNobyA9IEYsIGVycm9yID0gRiwgd2FybmluZyA9IEYsIG1lc3NhZ2UgPSBGfQ0KdXNlcl9wb3N0cyA8LSBwb3N0cyAlPiUNCiAgZmlsdGVyKFBvc3RUeXBlSWQgJWluJSBjKDEsMikgJiBPd25lclVzZXJJZCAlaW4lIHVzZXJzJElkKQ0KDQpwb3N0c19jb3VudCA8LSB1c2VyX3Bvc3RzICU+JQ0KICBjb3VudChPd25lclVzZXJJZCwgUG9zdFR5cGVJZCkNCnBvc3RzX2NvdW50IDwtIGRjYXN0KHBvc3RzX2NvdW50LCBPd25lclVzZXJJZCB+IFBvc3RUeXBlSWQsIHZhbHVlLnZhciA9ICJuIiwgZnVuLmFnZ3JlZ2F0ZSA9IHN1bSwgbmEucm0gPSBUKQ0KY29sbmFtZXMocG9zdHNfY291bnQpIDwtIGMoIk93bmVyVXNlcklkIiwgInF1ZXN0aW9uX2NvdW50IiwgImFuc3dlcl9jb3VudCIpDQoNCnBvc3RzX2NvdW50IDwtIGxlZnRfam9pbih1c2VycywgcG9zdHNfY291bnQsIGJ5ID0gYygiSWQiID0gIk93bmVyVXNlcklkIikpICU+JQ0KICBzZWxlY3QoSWQsIHF1ZXN0aW9uX2NvdW50LCBhbnN3ZXJfY291bnQpDQpwb3N0c19jb3VudFtpcy5uYShwb3N0c19jb3VudCldIDwtIDANCg0KcG9zdHNfY291bnQgPC0gcG9zdHNfY291bnQgJT4lDQogIG11dGF0ZShiaW5uZWRfbnVtYmVyX29mX3F1ZXN0aW9ucyA9IGN1dChxdWVzdGlvbl9jb3VudCwgYnJlYWtzID0gYygtMSwgMCwgMSwgNSwgMTAwMDAwKSksDQogICAgICAgICBiaW5uZWRfbnVtYmVyX29mX2Fuc3dlcnMgPSBjdXQoYW5zd2VyX2NvdW50LCBicmVha3MgPSBjKC0xLCAwLCAxLCA1LCAxMDAwMDApKSkNCg0KcG9zdHNfY291bnQgPC0gcG9zdHNfY291bnQgJT4lDQogIGNvdW50KGJpbm5lZF9udW1iZXJfb2ZfcXVlc3Rpb25zLCBiaW5uZWRfbnVtYmVyX29mX2Fuc3dlcnMpICU+JQ0KICBhcnJhbmdlKGRlc2MobikpDQoNCnBvc3RzX2NvdW50IDwtIHBvc3RzX2NvdW50ICU+JQ0KICBtdXRhdGUocGVyY2VudCA9IHJvdW5kKG4vc3VtKG4pLDIpKQ0KYGBgDQoNCiMjIyBTZWdtZW50eSB1xbx5dGtvd25pa8Ozdw0KIyMjIyBQZcWCbmEgaGlzdG9yaWEgb2QgMjAwOA0KUG9uacW8ZWogemFwcmV6ZW50b3dhbmUgc8SFIHNlZ21lbnR5IHXFvHl0a293bmlrw7N3IHcgcG9kemlhbGUgbmEgbGljemLEmSB1ZHppZWxvbnljaCBweXRhxYQgaSBvZHBvd2llZHppLiBXeWRhamUgc2nEmSwgxbxlIHphIGFrdHl3bnljaCB1xbx5dGtvd25pa8OzdyAtIHRha2ljaCwgxbxlIGljaCBha3R5d25vxZvEhyBqZXN0IHd5c3RhcmN6YWrEhWNhIGJ5IG3Ds2MgaWNoIG9jZW5pxIcgLSBuYWxlxbx5IHV6bmHEhyB1xbx5dGtvd25pa8OzdyB6IG1pbmltdW0gNSBwb3N0YW1pLCB3IHN6Y3plZ8OzbG5vxZtjaSBqZcWbbGkgbWFqxIVjIG1uaWVqIG5pxbwgNSBwb3N0w7N3LCBuaWUgb3RyenltYW5vIMW8YWRueWNoIHZvdGUnw7N3Lg0KDQpMZWdlbmRhOg0KDQpFdHlraWV0YSB8IE9waXMNCi0tLSB8IC0tLQ0KKC0xLDBdIHwgMCBwb3N0w7N3DQooMCwxXSB8IDEgcG9zdA0KKDEsNV0gfCAyLTUgcG9zdMOzdw0KKDUsIDFlKzA1XSB8IHBvd3nFvGVqIDUgcG9zdMOzdw0KDQpgYGB7ciBlY2hvID0gRiwgZXJyb3IgPSBGLCB3YXJuaW5nID0gRiwgbWVzc2FnZSA9IEZ9DQp1c2VyX3Bvc3RzIDwtIHBvc3RzICU+JQ0KICBmaWx0ZXIoUG9zdFR5cGVJZCAlaW4lIGMoMSwyKSAmIE93bmVyVXNlcklkICVpbiUgdXNlcnMkSWQpDQoNCnBvc3RzX2NvdW50IDwtIHVzZXJfcG9zdHMgJT4lDQogIGNvdW50KE93bmVyVXNlcklkLCBQb3N0VHlwZUlkKQ0KcG9zdHNfY291bnQgPC0gZGNhc3QocG9zdHNfY291bnQsIE93bmVyVXNlcklkIH4gUG9zdFR5cGVJZCwgdmFsdWUudmFyID0gIm4iLCBmdW4uYWdncmVnYXRlID0gc3VtLCBuYS5ybSA9IFQpDQpjb2xuYW1lcyhwb3N0c19jb3VudCkgPC0gYygiT3duZXJVc2VySWQiLCAicXVlc3Rpb25fY291bnQiLCAiYW5zd2VyX2NvdW50IikNCg0KcG9zdHNfY291bnQgPC0gbGVmdF9qb2luKHVzZXJzLCBwb3N0c19jb3VudCwgYnkgPSBjKCJJZCIgPSAiT3duZXJVc2VySWQiKSkgJT4lDQogIHNlbGVjdChJZCwgcXVlc3Rpb25fY291bnQsIGFuc3dlcl9jb3VudCkNCnBvc3RzX2NvdW50W2lzLm5hKHBvc3RzX2NvdW50KV0gPC0gMA0KDQpwb3N0c19jb3VudCA8LSBwb3N0c19jb3VudCAlPiUNCiAgbXV0YXRlKGJpbm5lZF9udW1iZXJfb2ZfcXVlc3Rpb25zID0gY3V0KHF1ZXN0aW9uX2NvdW50LCBicmVha3MgPSBjKC0xLCAwLCAxLCA1LCAxMDAwMDApKSwNCiAgICAgICAgIGJpbm5lZF9udW1iZXJfb2ZfYW5zd2VycyA9IGN1dChhbnN3ZXJfY291bnQsIGJyZWFrcyA9IGMoLTEsIDAsIDEsIDUsIDEwMDAwMCkpKQ0KDQpwb3N0c19jb3VudCA8LSBwb3N0c19jb3VudCAlPiUNCiAgY291bnQoYmlubmVkX251bWJlcl9vZl9xdWVzdGlvbnMsIGJpbm5lZF9udW1iZXJfb2ZfYW5zd2VycykgJT4lDQogIGFycmFuZ2UoZGVzYyhuKSkNCg0KcG9zdHNfY291bnQgPC0gcG9zdHNfY291bnQgJT4lDQogIG11dGF0ZShwZXJjZW50ID0gcm91bmQobi9zdW0obiksMikpDQpgYGANCg0KYGBge3IgZWNobyA9IEYsIGVycm9yID0gRiwgd2FybmluZyA9IEYsIG1lc3NhZ2UgPSBGfQ0KcGxvdF9seShwb3N0c19jb3VudCwgeCA9IH5iaW5uZWRfbnVtYmVyX29mX3F1ZXN0aW9ucywgeSA9IH5iaW5uZWRfbnVtYmVyX29mX2Fuc3dlcnMsIA0KICAgICAgICB0ZXh0ID0gfnBhc3RlMCgiTGljemJhIG9icy4gPSAiLCBuLCAiLCBQcm9jZW50ID0gIiwgcGVyY2VudCksDQogICAgICAgIHR5cGUgPSAnc2NhdHRlcicsIG1vZGUgPSAnbWFya2VycycsIG1hcmtlciA9IGxpc3Qoc2l6ZSA9IH5uLzEwMCwgb3BhY2l0eSA9IDAuNSkpICU+JQ0KICBsYXlvdXQodGl0bGUgPSAnU2VnbWVudGFjamEgdcW8eXRrb3duaWvDs3cgd3pnbMSZZGVtIGxpY3pieSBwb3N0w7N3JywNCiAgICAgICAgIHhheGlzID0gbGlzdCh0aXRsZSA9ICJMaWN6YmEgcHl0YcWEIiksDQogICAgICAgICB5YXhpcyA9IGxpc3QodGl0bGUgPSAiTGljemJhIG9kcG93aWVkemkiKSkNCmBgYA0KDQoqKioNCg0KIyMjIyDFmndpZcW8ZSBwb3N0eQ0KUG9uacW8ZWogemFwcmV6ZW50b3dhbmUgc8SFIHNlZ21lbnR5IHXFvHl0a293bmlrw7N3IGJpb3LEhWMgcG9kIHV3YWfEmSB0eWxrbyBwb3N0eSB6IG9zdGF0bmljaCAzIGxhdC4NCmBgYHtyIGVjaG8gPSBGLCBlcnJvciA9IEYsIHdhcm5pbmcgPSBGLCBtZXNzYWdlID0gRn0NCmZyZXNoX3VzZXJfcG9zdHMgPC0gcG9zdHMgJT4lDQogIGZpbHRlcihQb3N0VHlwZUlkICVpbiUgYygxLDIpICYgT3duZXJVc2VySWQgJWluJSB1c2VycyRJZCAmIGFzLkRhdGUoQ3JlYXRpb25EYXRlKSA+PSB0b2RheSgpIC0gKDM2NSozKSkNCg0KZnJlc2hfcG9zdHNfY291bnQgPC0gZnJlc2hfdXNlcl9wb3N0cyAlPiUNCiAgY291bnQoT3duZXJVc2VySWQsIFBvc3RUeXBlSWQpDQpmcmVzaF9wb3N0c19jb3VudCA8LSBkY2FzdChmcmVzaF9wb3N0c19jb3VudCwgT3duZXJVc2VySWQgfiBQb3N0VHlwZUlkLCB2YWx1ZS52YXIgPSAibiIsIGZ1bi5hZ2dyZWdhdGUgPSBzdW0sIG5hLnJtID0gVCkNCmNvbG5hbWVzKGZyZXNoX3Bvc3RzX2NvdW50KSA8LSBjKCJPd25lclVzZXJJZCIsICJxdWVzdGlvbl9jb3VudCIsICJhbnN3ZXJfY291bnQiKQ0KDQpmcmVzaF9wb3N0c19jb3VudCA8LSBsZWZ0X2pvaW4odXNlcnMsIGZyZXNoX3Bvc3RzX2NvdW50LCBieSA9IGMoIklkIiA9ICJPd25lclVzZXJJZCIpKSAlPiUNCiAgc2VsZWN0KElkLCBxdWVzdGlvbl9jb3VudCwgYW5zd2VyX2NvdW50KQ0KZnJlc2hfcG9zdHNfY291bnRbaXMubmEoZnJlc2hfcG9zdHNfY291bnQpXSA8LSAwDQoNCmZyZXNoX3Bvc3RzX2NvdW50IDwtIGZyZXNoX3Bvc3RzX2NvdW50ICU+JQ0KICBtdXRhdGUoYmlubmVkX251bWJlcl9vZl9xdWVzdGlvbnMgPSBjdXQocXVlc3Rpb25fY291bnQsIGJyZWFrcyA9IGMoLTEsIDAsIDEsIDUsIDEwMDAwMCkpLA0KICAgICAgICAgYmlubmVkX251bWJlcl9vZl9hbnN3ZXJzID0gY3V0KGFuc3dlcl9jb3VudCwgYnJlYWtzID0gYygtMSwgMCwgMSwgNSwgMTAwMDAwKSkpDQoNCmZyZXNoX3Bvc3RzX2NvdW50IDwtIGZyZXNoX3Bvc3RzX2NvdW50ICU+JQ0KICBjb3VudChiaW5uZWRfbnVtYmVyX29mX3F1ZXN0aW9ucywgYmlubmVkX251bWJlcl9vZl9hbnN3ZXJzKSAlPiUNCiAgYXJyYW5nZShkZXNjKG4pKQ0KDQpmcmVzaF9wb3N0c19jb3VudCA8LSBmcmVzaF9wb3N0c19jb3VudCAlPiUNCiAgbXV0YXRlKHBlcmNlbnQgPSByb3VuZChuL3N1bShuKSwyKSkNCmBgYA0KDQpgYGB7ciBlY2hvID0gRiwgZXJyb3IgPSBGLCB3YXJuaW5nID0gRiwgbWVzc2FnZSA9IEZ9DQpwbG90X2x5KGZyZXNoX3Bvc3RzX2NvdW50LCB4ID0gfmJpbm5lZF9udW1iZXJfb2ZfcXVlc3Rpb25zLCB5ID0gfmJpbm5lZF9udW1iZXJfb2ZfYW5zd2VycywgDQogICAgICAgIHRleHQgPSB+cGFzdGUwKCJMaWN6YmEgb2JzLiA9ICIsIG4sICIsIFByb2NlbnQgPSAiLCBwZXJjZW50KSwNCiAgICAgICAgdHlwZSA9ICdzY2F0dGVyJywgbW9kZSA9ICdtYXJrZXJzJywgbWFya2VyID0gbGlzdChzaXplID0gfm4vMTAwLCBvcGFjaXR5ID0gMC41KSkgJT4lDQogIGxheW91dCh0aXRsZSA9ICdTZWdtZW50YWNqYSB1xbx5dGtvd25pa8OzdyAtIG9zdGF0bmllIDMgbGF0YScsDQogICAgICAgICB4YXhpcyA9IGxpc3QodGl0bGUgPSAiTGljemJhIHB5dGHFhCIpLA0KICAgICAgICAgeWF4aXMgPSBsaXN0KHRpdGxlID0gIkxpY3piYSBvZHBvd2llZHppIikpDQpgYGANCg0KKioqDQoNCiMjIyBBbmFsaXphIG9kcG93aWVkemkNCg0KYGBge3IgcmVzdWx0cyA9IEYsIGVjaG8gPSBGLCBlcnJvciA9IEYsIHdhcm5pbmcgPSBGLCBtZXNzYWdlID0gRn0NCmFuc3dlcklkIDwtIHBvc3R0eXBlcyAlPiUNCiAgZmlsdGVyKE5hbWUgPT0gIkFuc3dlciIpDQoNCnBvc3RzX2Fuc3dlcnMgPC0gcG9zdHMgJT4lDQogIGZpbHRlcihQb3N0VHlwZUlkID09IGFuc3dlcklkWzEsMV0gJiBPd25lclVzZXJJZCAlaW4lIHVzZXJzJElkKSAlPiUNCiAgbXV0YXRlKENyZWF0aW9uRGF0ZSA9IGFzLkRhdGUoQ3JlYXRpb25EYXRlKSkNCmBgYA0KDQpgYGB7ciBlY2hvID0gRiwgZXJyb3IgPSBGLCB3YXJuaW5nID0gRiwgbWVzc2FnZSA9IEZ9DQphbnN3ZXJzX2NvdW50IDwtIHBvc3RzX2Fuc3dlcnMgJT4lDQogIGdyb3VwX2J5KE93bmVyVXNlcklkKSAlPiUNCiAgc3VtbWFyaXNlKGNvdW50ID0gbigpKQ0KDQphbnN3ZXJzX2NvdW50IDwtIGxlZnRfam9pbih1c2VycywgYW5zd2Vyc19jb3VudCwgYnkgPSBjKCJJZCIgPSAiT3duZXJVc2VySWQiKSkgJT4lDQogIHNlbGVjdChJZCwgY291bnQpDQoNCmFuc3dlcnNfY291bnRbaXMubmEoYW5zd2Vyc19jb3VudCldIDwtIDANCg0KYW5zd2Vyc19jb3VudCA8LSBhbnN3ZXJzX2NvdW50ICU+JQ0KICBncm91cF9ieShudW1iZXJfb2ZfYW5zd2VycyA9IGNvdW50KSAlPiUNCiAgc3VtbWFyaXNlKGNvdW50ID0gbigpKSAlPiUNCiAgZGF0YS5mcmFtZSgpICU+JQ0KICBtdXRhdGUoYmlubmVkX251bWJlcl9vZl9hbnN3ZXJzID0gY3V0KG51bWJlcl9vZl9hbnN3ZXJzLCBicmVha3MgPSBjKC0xLCAwLCAxLCA1LCAxMCwgMTAwLCAxMDAwMDApKSkgJT4lDQogIGdyb3VwX2J5KGJpbm5lZF9udW1iZXJfb2ZfYW5zd2VycykgJT4lDQogIHN1bW1hcmlzZShjb3VudCA9IHN1bShjb3VudCkpDQpgYGANCg0KIyMjIyBMaWN6YmEgb2Rwb3dpZWR6aSB1xbx5dGtvd25pa8OzdyAtIHBlxYJuYSBoaXN0b3JpYSBvZCAyMDA4DQoNCldpxJlrc3pvxZvEhyB1xbx5dGtvd25pa8OzdyAoYHIgYXMubnVtZXJpYygxMDAqcm91bmQoYW5zd2Vyc19jb3VudFsxLDJdL3N1bShhbnN3ZXJzX2NvdW50JGNvdW50KSw0KSlgJSkgbmlnZHkgbmllIG5hcGlzYcWCYSDFvGFkbmVqIG9kcG93aWVkemkuIEtvbGVqbmUgYHIgYXMubnVtZXJpYygxMDAqcm91bmQoYW5zd2Vyc19jb3VudFsyLDJdL3N1bShhbnN3ZXJzX2NvdW50JGNvdW50KSw0KSlgJSBuYXBpc2HFgm8gdHlsa28gMSBvZHBvd2llZMW6LCBgciBhcy5udW1lcmljKDEwMCpyb3VuZChhbnN3ZXJzX2NvdW50WzMsMl0vc3VtKGFuc3dlcnNfY291bnQkY291bnQpLDQpKWAlIG5hcGlzYcWCbyBwb21pxJlkenkgMSBhIDUgb2Rwb3dpZWR6aS4gS29sZWpuZSBwcnplZHppYcWCeSBsaWN6Ynkgb2Rwb3dpZWR6aSBzxIUgemFwcmV6ZW50b3dhbmUgbmEgcG9uacW8c3p5bSB3eWtyZXNpZS4NCg0KYGBge3IgZWNobyA9IEYsIGVycm9yID0gRiwgd2FybmluZyA9IEYsIG1lc3NhZ2UgPSBGfQ0KcGxvdF9seShhbnN3ZXJzX2NvdW50LCBsYWJlbHMgPSB+YmlubmVkX251bWJlcl9vZl9hbnN3ZXJzLCB2YWx1ZXMgPSB+Y291bnQsIHR5cGUgPSAncGllJywgc29ydCA9IEYpICU+JQ0KICBsYXlvdXQodGl0bGUgPSAnTGljemJhIG9kcG93aWVkemkgbmFwaXNhbnljaCBwcnpleiB1xbx5dGtvd25pa8OzdycpDQpgYGANCg0KKioqDQoNCiMjIyMgTGljemJhIG9kcG93aWVkemkgdyBjemFzaWUNCk9kcG93aWVkemkgaXN0bmllasSFY2UgdyBiYXppZSBwb2Nob2R6xIUgeiBva3Jlc3UgYHIgYXMuRGF0ZShtaW4ocG9zdHNfYW5zd2VycyRDcmVhdGlvbkRhdGUpKWAgLSBgciBhcy5EYXRlKG1heChwb3N0c19hbnN3ZXJzJENyZWF0aW9uRGF0ZSkpYC4gV2lkYcSHLCDFvGUgbGF0YSAyMDA4IC0gMjAxMyBiecWCeSBjemFzZW0gd3pyb3N0dSB3b2x1bWVudSBvZHBvd2llZHppLCBwb2RjemFzIGdkeSBvZCAyMDEzIG1pZXNpxJljem5hIGxpY3piYSBvZHBvd2llZHppIHVzdGFiaWxpem93YcWCYSBzacSZLiBQcmV6ZW50dWplIHRvIHBvbmnFvHN6eSB3eWtyZXMuDQpgYGB7ciBlY2hvID0gRiwgZXJyb3IgPSBGLCB3YXJuaW5nID0gRiwgbWVzc2FnZSA9IEZ9DQphbnN3ZXJzX2RhdGVzIDwtIHBvc3RzX2Fuc3dlcnMgJT4lDQogIGdyb3VwX2J5KGNyZWF0aW9uX2RhdGUgPSBDcmVhdGlvbkRhdGUpICU+JQ0KICBzdW1tYXJpc2UoY291bnQgPSBuKCkpDQoNCmNyZWF0aW9uX2RhdGVzIDwtIGRhdGEuZnJhbWUoY3JlYXRpb25fZGF0ZSA9IHNlcShhcy5EYXRlKG1pbihhbnN3ZXJzX2RhdGVzJGNyZWF0aW9uX2RhdGUpKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhcy5EYXRlKG1heChhbnN3ZXJzX2RhdGVzJGNyZWF0aW9uX2RhdGUpKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBieSA9ICJkYXkiKSkNCg0KYW5zd2Vyc19kYXRlcyA8LSBsZWZ0X2pvaW4oY3JlYXRpb25fZGF0ZXMsIGFuc3dlcnNfZGF0ZXMpICU+JQ0KICBhcnJhbmdlKGNyZWF0aW9uX2RhdGUpICU+JQ0KICBncm91cF9ieShjcmVhdGlvbl9kYXRlID0gZm9ybWF0KGNyZWF0aW9uX2RhdGUsICclWS0lbScpKSAlPiUNCiAgc3VtbWFyaXNlKGNvdW50ID0gc3VtKGNvdW50LCBuYS5ybSA9IFQpKQ0KDQpwbG90X2x5KGFuc3dlcnNfZGF0ZXMsIHggPSB+Y3JlYXRpb25fZGF0ZSwgeSA9IH5jb3VudCwgdHlwZSA9ICdzY2F0dGVyJywgbW9kZSA9ICdsaW5lcycpICU+JQ0KICBsYXlvdXQodGl0bGUgPSAnTGljemJhIG9kcG93aWVkemkgdyB6YWxlxbxub8WbY2kgb2QgQ3JlYXRpb25EYXRlJykNCmBgYA0KDQoqKioNCg0KIyMjIyBMaWN6YmEgxZt3aWXFvHljaCBvZHBvd2llZHppIHXFvHl0a293bmlrw7N3DQpBYnkgdXp5c2thxIcgYWt0dWFsbsSFIGluZm9ybWFjasSZIG8gemFhbmdhxbxvd2FuaXUgdcW8eXRrb3duaWthIG5hIHBsYXRmb3JtaWUgd2FydG8gb2dyYW5pY3p5xIcgYW5hbGl6xJkgdHlsa28gZG8gd3Bpc8OzdyBucC4geiBvc3RhdG5pY2ggMyBsYXQuIA0KYGBge3IgZWNobyA9IEYsIGVycm9yID0gRiwgd2FybmluZyA9IEYsIG1lc3NhZ2UgPSBGfQ0KZnJlc2hfYW5zd2Vyc19jb3VudCA8LSBwb3N0c19hbnN3ZXJzICU+JQ0KICBmaWx0ZXIoQ3JlYXRpb25EYXRlID49IHRvZGF5KCkgLSAoMzY1KjMpKSAlPiUNCiAgZ3JvdXBfYnkoT3duZXJVc2VySWQpICU+JQ0KICBzdW1tYXJpc2UoY291bnQgPSBuKCkpDQoNCmZyZXNoX2Fuc3dlcnNfY291bnQgPC0gbGVmdF9qb2luKHVzZXJzLCBmcmVzaF9hbnN3ZXJzX2NvdW50LCBieSA9IGMoIklkIiA9ICJPd25lclVzZXJJZCIpKSAlPiUNCiAgc2VsZWN0KElkLCBjb3VudCkNCg0KZnJlc2hfYW5zd2Vyc19jb3VudFtpcy5uYShmcmVzaF9hbnN3ZXJzX2NvdW50KV0gPC0gMA0KDQpmcmVzaF9hbnN3ZXJzX2NvdW50IDwtIGZyZXNoX2Fuc3dlcnNfY291bnQgJT4lDQogIGdyb3VwX2J5KG51bWJlcl9vZl9hbnN3ZXJzID0gY291bnQpICU+JQ0KICBzdW1tYXJpc2UoY291bnQgPSBuKCkpICU+JQ0KICBkYXRhLmZyYW1lKCkgJT4lDQogIG11dGF0ZShiaW5uZWRfbnVtYmVyX29mX2Fuc3dlcnMgPSBjdXQobnVtYmVyX29mX2Fuc3dlcnMsIGJyZWFrcyA9IGMoLTEsIDAsIDEsIDUsIDEwLCAxMDAsIDEwMDAwMCkpKSAlPiUNCiAgZ3JvdXBfYnkoYmlubmVkX251bWJlcl9vZl9hbnN3ZXJzKSAlPiUNCiAgc3VtbWFyaXNlKGNvdW50ID0gc3VtKGNvdW50KSkNCmBgYA0KDQpPZ3JhbmljemFqxIVjIG9kcG93aWVkemkgdHlsa28gZG8gb3N0YXRuaWNoIDMgbGF0IHdpZGHEhywgxbxlIG9nw7NsbnkgcG96aW9tIHphYW5nYcW8b3dhbmlhIGplc3QgbmnFvHN6eSBuacW8IGFuYWxpenVqxIVjIGNhxYLEhSBiYXrEmSBkYW55Y2guIFdpxJlrc3pvxZvEhyB1xbx5dGtvd25pa8OzdyAoYHIgYXMubnVtZXJpYygxMDAqcm91bmQoZnJlc2hfYW5zd2Vyc19jb3VudFsxLDJdL3N1bShmcmVzaF9hbnN3ZXJzX2NvdW50JGNvdW50KSw0KSlgJSkgbmlnZHkgbmllIG5hcGlzYcWCYSDFvGFkbmVqIG9kcG93aWVkemkuIEtvbGVqbmUgYHIgYXMubnVtZXJpYygxMDAqcm91bmQoZnJlc2hfYW5zd2Vyc19jb3VudFsyLDJdL3N1bShmcmVzaF9hbnN3ZXJzX2NvdW50JGNvdW50KSw0KSlgJSBuYXBpc2HFgm8gdHlsa28gMSBvZHBvd2llZMW6LCBgciBhcy5udW1lcmljKDEwMCpyb3VuZChmcmVzaF9hbnN3ZXJzX2NvdW50WzMsMl0vc3VtKGZyZXNoX2Fuc3dlcnNfY291bnQkY291bnQpLDQpKWAlIG5hcGlzYcWCbyBwb21pxJlkenkgMSBhIDUgb2Rwb3dpZWR6aS4gS29sZWpuZSBwcnplZHppYcWCeSBsaWN6Ynkgb2Rwb3dpZWR6aSBzxIUgemFwcmV6ZW50b3dhbmUgbmEgcG9uacW8c3p5bSB3eWtyZXNpZS4NCg0KYGBge3IgZWNobyA9IEYsIGVycm9yID0gRiwgd2FybmluZyA9IEYsIG1lc3NhZ2UgPSBGfQ0KcGxvdF9seShmcmVzaF9hbnN3ZXJzX2NvdW50LCBsYWJlbHMgPSB+YmlubmVkX251bWJlcl9vZl9hbnN3ZXJzLCB2YWx1ZXMgPSB+Y291bnQsIHR5cGUgPSAncGllJywgc29ydCA9IEYpICU+JQ0KICBsYXlvdXQodGl0bGUgPSAnTGljemJhIG9kcG93aWVkemkgLSBvc3RhdG5pZSAzIGxhdGEnKQ0KYGBgDQoNCioqKg0KDQojIyMgQW5hbGl6YSBweXRhxYQNCg0KYGBge3IgZWNobyA9IEYsIGVycm9yID0gRiwgd2FybmluZyA9IEYsIG1lc3NhZ2UgPSBGfQ0KcXVlc3Rpb25JZCA8LSBwb3N0dHlwZXMgJT4lDQogIGZpbHRlcihOYW1lID09ICJRdWVzdGlvbiIpDQoNCnBvc3RzX3F1ZXN0aW9ucyA8LSBwb3N0cyAlPiUNCiAgZmlsdGVyKFBvc3RUeXBlSWQgPT0gcXVlc3Rpb25JZFsxLDFdICYgT3duZXJVc2VySWQgJWluJSB1c2VycyRJZCkgJT4lDQogIG11dGF0ZShDcmVhdGlvbkRhdGUgPSBhcy5EYXRlKENyZWF0aW9uRGF0ZSkpDQpgYGANCg0KYGBge3IgZWNobyA9IEYsIGVycm9yID0gRiwgd2FybmluZyA9IEYsIG1lc3NhZ2UgPSBGfQ0KcXVlc3Rpb25zX2NvdW50IDwtIHBvc3RzX3F1ZXN0aW9ucyAlPiUNCiAgZ3JvdXBfYnkoT3duZXJVc2VySWQpICU+JQ0KICBzdW1tYXJpc2UoY291bnQgPSBuKCkpDQoNCnF1ZXN0aW9uc19jb3VudCA8LSBsZWZ0X2pvaW4odXNlcnMsIHF1ZXN0aW9uc19jb3VudCwgYnkgPSBjKCJJZCIgPSAiT3duZXJVc2VySWQiKSkgJT4lDQogIHNlbGVjdChJZCwgY291bnQpDQoNCnF1ZXN0aW9uc19jb3VudFtpcy5uYShxdWVzdGlvbnNfY291bnQpXSA8LSAwDQoNCnF1ZXN0aW9uc19jb3VudCA8LSBxdWVzdGlvbnNfY291bnQgJT4lDQogIGdyb3VwX2J5KG51bWJlcl9vZl9xdWVzdGlvbnMgPSBjb3VudCkgJT4lDQogIHN1bW1hcmlzZShjb3VudCA9IG4oKSkgJT4lDQogIGRhdGEuZnJhbWUoKSAlPiUNCiAgbXV0YXRlKGJpbm5lZF9udW1iZXJfb2ZfcXVlc3Rpb25zID0gY3V0KG51bWJlcl9vZl9xdWVzdGlvbnMsIGJyZWFrcyA9IGMoLTEsIDAsIDEsIDUsIDEwLCAxMDAsIDEwMDAwMCkpKSAlPiUNCiAgZ3JvdXBfYnkoYmlubmVkX251bWJlcl9vZl9xdWVzdGlvbnMpICU+JQ0KICBzdW1tYXJpc2UoY291bnQgPSBzdW0oY291bnQpKQ0KYGBgDQoNCiMjIyMgTGljemJhIHB5dGHFhCB1xbx5dGtvd25pa8Ozdw0KDQpJc3RvdG5pZSB3acSZa3N6YSBjesSZxZvEhyB1xbx5dGtvd25pa8OzdyB6YXBvc3Rvd2HFgmEgY28gbmFqbW5pZWogamVkbm8gcHl0YW5pZSBuacW8IG9kcG93aWVkxbogLSBgciAxMDAtYXMubnVtZXJpYygxMDAqcm91bmQocXVlc3Rpb25zX2NvdW50WzEsMl0vc3VtKHF1ZXN0aW9uc19jb3VudCRjb3VudCksNCkpYCUgdnMgYHIgMTAwLWFzLm51bWVyaWMoMTAwKnJvdW5kKGFuc3dlcnNfY291bnRbMSwyXS9zdW0oYW5zd2Vyc19jb3VudCRjb3VudCksNCkpYCUNCg0KV2nEmWtzem/Fm8SHIHXFvHl0a293bmlrw7N3IChgciBhcy5udW1lcmljKDEwMCpyb3VuZChxdWVzdGlvbnNfY291bnRbMSwyXS9zdW0ocXVlc3Rpb25zX2NvdW50JGNvdW50KSw0KSlgJSkgbmlnZHkgbmllIG5hcGlzYcWCYSDFvGFkbmVnbyBweXRhbmlhLiBLb2xlam5lIGByIGFzLm51bWVyaWMoMTAwKnJvdW5kKHF1ZXN0aW9uc19jb3VudFsyLDJdL3N1bShxdWVzdGlvbnNfY291bnQkY291bnQpLDQpKWAlIG5hcGlzYcWCbyB0eWxrbyAxIHB5dGFuaWUsIGByIGFzLm51bWVyaWMoMTAwKnJvdW5kKHF1ZXN0aW9uc19jb3VudFszLDJdL3N1bShxdWVzdGlvbnNfY291bnQkY291bnQpLDQpKWAlIG5hcGlzYcWCbyBwb21pxJlkenkgMSBhIDUgcHl0YW5pYW1pLiBLb2xlam5lIHByemVkemlhxYJ5IGxpY3pieSBweXRhxYQgc8SFIHphcHJlemVudG93YW5lIG5hIHBvbmnFvHN6eW0gd3lrcmVzaWUuDQoNCmBgYHtyIGVjaG8gPSBGLCBlcnJvciA9IEYsIHdhcm5pbmcgPSBGLCBtZXNzYWdlID0gRn0NCnBsb3RfbHkocXVlc3Rpb25zX2NvdW50LCBsYWJlbHMgPSB+YmlubmVkX251bWJlcl9vZl9xdWVzdGlvbnMsIHZhbHVlcyA9IH5jb3VudCwgdHlwZSA9ICdwaWUnLCBzb3J0ID0gRikgJT4lDQogIGxheW91dCh0aXRsZSA9ICdMaWN6YmEgcHl0YcWEIG5hcGlzYW55Y2ggcHJ6ZXogdcW8eXRrb3duaWvDs3cnKQ0KYGBgDQoNCioqKg0KDQojIyMjIExpY3piYSBweXRhxYQgdyBjemFzaWUNClB5dGFuaWEgaXN0bmllasSFY2UgdyBiYXppZSBwb2Nob2R6xIUgeiBva3Jlc3UgYHIgYXMuRGF0ZShtaW4ocG9zdHNfcXVlc3Rpb25zJENyZWF0aW9uRGF0ZSkpYCAtIGByIGFzLkRhdGUobWF4KHBvc3RzX3F1ZXN0aW9ucyRDcmVhdGlvbkRhdGUpKWAuIFR1LCB3IG9kcsOzxbxuaWVuaXUgb2Qgb2Rwb3dpZWR6aSwgbWFteSBkbyBjenluaWVuaWEgeiB0cmVuZGVtIHN0YWxlIHJvc27EhWN5bS4gTW/FvGUgdG8gb3puYWN6YcSHLCDFvGUgcG9weXQgbmEgd2llZHrEmSB3Y2nEhcW8IHJvxZtuaWUsIGplZG5hayBwb2Rhxbwgb2Rwb3dpYWRhasSFY3ljaCBzacSZIHVzdGFiaWxpem93YcWCYSBraWxrYSBsYXQgdGVtdSBpIG5pZSBwcnp5cmFzdGEgcHJvcG9yY2pvbmFsbmllIGRvIHBvcHl0dS4NCmBgYHtyIGVjaG8gPSBGLCBlcnJvciA9IEYsIHdhcm5pbmcgPSBGLCBtZXNzYWdlID0gRn0NCnF1ZXN0aW9uc19kYXRlcyA8LSBwb3N0c19xdWVzdGlvbnMgJT4lDQogIGdyb3VwX2J5KGNyZWF0aW9uX2RhdGUgPSBDcmVhdGlvbkRhdGUpICU+JQ0KICBzdW1tYXJpc2UoY291bnQgPSBuKCkpDQoNCmNyZWF0aW9uX2RhdGVzIDwtIGRhdGEuZnJhbWUoY3JlYXRpb25fZGF0ZSA9IHNlcShhcy5EYXRlKG1pbihxdWVzdGlvbnNfZGF0ZXMkY3JlYXRpb25fZGF0ZSkpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFzLkRhdGUobWF4KHF1ZXN0aW9uc19kYXRlcyRjcmVhdGlvbl9kYXRlKSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnkgPSAiZGF5IikpDQoNCnF1ZXN0aW9uc19kYXRlcyA8LSBsZWZ0X2pvaW4oY3JlYXRpb25fZGF0ZXMsIHF1ZXN0aW9uc19kYXRlcykgJT4lDQogIGFycmFuZ2UoY3JlYXRpb25fZGF0ZSkgJT4lDQogIGdyb3VwX2J5KGNyZWF0aW9uX2RhdGUgPSBmb3JtYXQoY3JlYXRpb25fZGF0ZSwgJyVZLSVtJykpICU+JQ0KICBzdW1tYXJpc2UoY291bnQgPSBzdW0oY291bnQsIG5hLnJtID0gVCkpDQoNCnBsb3RfbHkocXVlc3Rpb25zX2RhdGVzLCB4ID0gfmNyZWF0aW9uX2RhdGUsIHkgPSB+Y291bnQsIHR5cGUgPSAnc2NhdHRlcicsIG1vZGUgPSAnbGluZXMnKSAlPiUNCiAgbGF5b3V0KHRpdGxlID0gJ0xpY3piYSBweXRhxYQgdyB6YWxlxbxub8WbY2kgb2QgQ3JlYXRpb25EYXRlJykNCmBgYA0KDQojIyMjIExpY3piYSDFm3dpZcW8eWNoIHB5dGHFhCB1xbx5dGtvd25pa8Ozdw0KQWJ5IHV6eXNrYcSHIGFrdHVhbG7EhSBpbmZvcm1hY2rEmSBvIHphYW5nYcW8b3dhbml1IHXFvHl0a293bmlrYSBuYSBwbGF0Zm9ybWllIHdhcnRvIG9ncmFuaWN6ecSHIGFuYWxpesSZIHR5bGtvIGRvIHdwaXPDs3cgbnAuIHogb3N0YXRuaWNoIDMgbGF0LiANCmBgYHtyIGVjaG8gPSBGLCBlcnJvciA9IEYsIHdhcm5pbmcgPSBGLCBtZXNzYWdlID0gRn0NCmZyZXNoX3F1ZXN0aW9uc19jb3VudCA8LSBwb3N0c19xdWVzdGlvbnMgJT4lDQogIGZpbHRlcihDcmVhdGlvbkRhdGUgPj0gdG9kYXkoKSAtICgzNjUqMykpICU+JQ0KICBncm91cF9ieShPd25lclVzZXJJZCkgJT4lDQogIHN1bW1hcmlzZShjb3VudCA9IG4oKSkNCg0KZnJlc2hfcXVlc3Rpb25zX2NvdW50IDwtIGxlZnRfam9pbih1c2VycywgZnJlc2hfcXVlc3Rpb25zX2NvdW50LCBieSA9IGMoIklkIiA9ICJPd25lclVzZXJJZCIpKSAlPiUNCiAgc2VsZWN0KElkLCBjb3VudCkNCg0KZnJlc2hfcXVlc3Rpb25zX2NvdW50W2lzLm5hKGZyZXNoX3F1ZXN0aW9uc19jb3VudCldIDwtIDANCg0KZnJlc2hfcXVlc3Rpb25zX2NvdW50IDwtIGZyZXNoX3F1ZXN0aW9uc19jb3VudCAlPiUNCiAgZ3JvdXBfYnkobnVtYmVyX29mX3F1ZXN0aW9ucyA9IGNvdW50KSAlPiUNCiAgc3VtbWFyaXNlKGNvdW50ID0gbigpKSAlPiUNCiAgZGF0YS5mcmFtZSgpICU+JQ0KICBtdXRhdGUoYmlubmVkX251bWJlcl9vZl9xdWVzdGlvbnMgPSBjdXQobnVtYmVyX29mX3F1ZXN0aW9ucywgYnJlYWtzID0gYygtMSwgMCwgMSwgNSwgMTAsIDEwMCwgMTAwMDAwKSkpICU+JQ0KICBncm91cF9ieShiaW5uZWRfbnVtYmVyX29mX3F1ZXN0aW9ucykgJT4lDQogIHN1bW1hcmlzZShjb3VudCA9IHN1bShjb3VudCkpDQpgYGANCg0KT2dyYW5pY3phasSFYyBweXRhbmlhIHR5bGtvIGRvIG9zdGF0bmljaCAzIGxhdCB3aWRhxIcsIMW8ZSBvZ8OzbG55IHBvemlvbSB6YWFuZ2HFvG93YW5pYSBqZXN0IG5pxbxzenkgbmnFvCBhbmFsaXp1asSFYyBjYcWCxIUgYmF6xJkgZGFueWNoLiBXacSZa3N6b8WbxIcgdcW8eXRrb3duaWvDs3cgKGByIGFzLm51bWVyaWMoMTAwKnJvdW5kKGZyZXNoX3F1ZXN0aW9uc19jb3VudFsxLDJdL3N1bShmcmVzaF9xdWVzdGlvbnNfY291bnQkY291bnQpLDQpKWAlKSBuaWdkeSBuaWUgbmFwaXNhxYJhIMW8YWRuZWdvIHB5dGFuaWEuIEtvbGVqbmUgYHIgYXMubnVtZXJpYygxMDAqcm91bmQoZnJlc2hfcXVlc3Rpb25zX2NvdW50WzIsMl0vc3VtKGZyZXNoX3F1ZXN0aW9uc19jb3VudCRjb3VudCksNCkpYCUgbmFwaXNhxYJvIHR5bGtvIDEgcHl0YW5pZSwgYHIgYXMubnVtZXJpYygxMDAqcm91bmQoZnJlc2hfcXVlc3Rpb25zX2NvdW50WzMsMl0vc3VtKGZyZXNoX3F1ZXN0aW9uc19jb3VudCRjb3VudCksNCkpYCUgbmFwaXNhxYJvIHBvbWnEmWR6eSAxIGEgNSBweXRhbmlhbWkuIEtvbGVqbmUgcHJ6ZWR6aWHFgnkgbGljemJ5IHB5dGHFhCBzxIUgemFwcmV6ZW50b3dhbmUgbmEgcG9uacW8c3p5bSB3eWtyZXNpZS4NCg0KYGBge3IgZWNobyA9IEYsIGVycm9yID0gRiwgd2FybmluZyA9IEYsIG1lc3NhZ2UgPSBGfQ0KcGxvdF9seShmcmVzaF9xdWVzdGlvbnNfY291bnQsIGxhYmVscyA9IH5iaW5uZWRfbnVtYmVyX29mX3F1ZXN0aW9ucywgdmFsdWVzID0gfmNvdW50LCB0eXBlID0gJ3BpZScsIHNvcnQgPSBGKSAlPiUNCiAgbGF5b3V0KHRpdGxlID0gJ0xpY3piYSBweXRhxYQgLSBvc3RhdG5pZSAzIGxhdGEnKQ0KYGBgDQoNCioqKg0KDQojIyMgUG96b3N0YcWCZSB0eXB5IHBvc3TDs3cNClcgU3RhY2tvdmVyZmxvdyBqZXN0IHpkZWZpbmlvd2FuZSBraWxrYSB0eXDDs3cgcG9zdMOzdywgcG96YSBuYWpwb3B1bGFybmllanN6eW1pIGN6eWxpIHB5dGFuaWFtaSBpIG9kcG93aWVkemlhbWkuIEplZG5hayBpbm5lIHR5cHkgcG9zdMOzdyBuacW8IHRlIGFuYWxpem93YW5lIHBvd3nFvGVqIHPEhSB6YW5pZWRieXdhbG5pZSByemFka2llLCB3acSZYyBwb21pamFteSBqZSB3IGFuYWxpemllLiBJY2ggcm96a8WCYWQgamVzdCB6YXByZXplbnRvd2FueSBwb25pxbxlai4NCg0KYGBge3J9DQpsZWZ0X2pvaW4ocG9zdHMsIHBvc3R0eXBlcywgYnkgPSBjKCJQb3N0VHlwZUlkIiA9ICJJZCIpKSAlPiUgY291bnQoUG9zdFR5cGVOYW1lID0gTmFtZSkgJT4lIGFycmFuZ2UoZGVzYyhuKSkNCmBgYA0KDQoNCg0KIyMgQW5hbGl6YSB0YWfDs3cNCg0KIyMjIExpY3plYm5vxZtjaSB0YWfDs3cNCmBgYHtyIGVjaG8gPSBGLCBlcnJvciA9IEYsIHdhcm5pbmcgPSBGLCBtZXNzYWdlID0gRn0NCnRhZ3NfY291bnQgPC0gcG9zdF90YWdzICU+JQ0KICBjb3VudChUYWdOYW1lKSAlPiUNCiAgYXJyYW5nZShkZXNjKG4pKQ0KDQp0YWdzX2RlbnNpdHkgPC0gdGFnc19jb3VudCAlPiUNCiAgbXV0YXRlKHBlcmMgPSBuL3N1bShuLCBuYS5ybSA9IFQpLA0KICAgICAgICAgY3VtX3BlcmMgPSByb3VuZChjdW1zdW0ocGVyYyksMiksDQogICAgICAgICBiaW5uZWRfY3VtX3BlcmMgPSBjdXQoY3VtX3BlcmMsIGJyZWFrcyA9IGMoMCwgMC4yNSwgMC41LCAwLjc1LCAxKSkpICU+JQ0KICBncm91cF9ieShiaW5uZWRfY3VtX3BlcmMpICU+JQ0KICBzdW1tYXJpc2UobnVtYmVyX29mX3RhZ3MgPSBuKCkpDQpgYGANCg0KVyBuYXN6ZWogYmF6aWUgcG9zdMOzdyB3eXN0xJlwdWplIGByIG5yb3codGFnc19jb3VudClgIHVuaWthbG55Y2ggdGFnw7N3LiBSb3prxYJhZCDEh3dpYXJ0a293eSB3eXN0xJlwb3dhbmlhIHdzenlzdGtpY2ggdGFnw7N3IHd5Z2zEhWRhIG5hc3TEmXB1asSFY286DQoNCiogMjUlIHdzenlzdGtpY2ggd3lzdMSZcHVqxIVjeWNoIHRhZ8OzdyB0byBgciBhcy5udW1lcmljKHRhZ3NfZGVuc2l0eVsxLDJdKWAgdW5pa2FsbnljaCB0YWfDs3csIGEgZG9rxYJhZG5pZTogYHIgYXMuY2hhcmFjdGVyKHRhZ3NfY291bnRbMTphcy5pbnRlZ2VyKHRhZ3NfZGVuc2l0eVsxLDJdKSwxXSlgDQoqIEtvbGVqbmUgMjUlIHdzenlzdGtpY2ggdGFnw7N3IHRvIGByIGFzLm51bWVyaWModGFnc19kZW5zaXR5WzIsMl0pYCB1bmlrYWxueWNoIHRhZ8Ozdw0KKiBLb2xlam5lIDI1JSB3c3p5c3RraWNoIHRhZ8OzdyB0byBgciBhcy5udW1lcmljKHRhZ3NfZGVuc2l0eVszLDJdKWAgdW5pa2FsbnljaCB0YWfDs3cNCiogT3N0YXRuaWUgMjUlIHdzenlzdGtpY2ggdGFnw7N3IHRvIGByIGFzLmludGVnZXIodGFnc19kZW5zaXR5WzQsMl0pYCB1bmlrYWxueWNoIHRhZ8Ozdw0KDQoqKioNCg0KIyMjIENobXVyYSBzxYLDs3cNClBvbmnFvGVqIHphcHJlemVudG93YW55IGplc3Qgcm96a8WCYWQgbmFqcG9wdWxhcm5pZWpzenljaCB0YWfDs3cgKHd5c3TEmXB1asSFY3ljaCB3acSZY2VqIG5pxbwgMzUwIHJhenkpIHpnb2RuaWUgeiBjesSZc3RvxZtjacSFIGljaCB3eXN0xJlwb3dhbmlhIHcgcG9zdGFjaC4gSW0gd2nEmWtzenkgcm96bWlhciBzxYJvd2EsIHR5bSBjesSZxZtjaWVqIHd5c3TEmXB1amUuDQoNCkphayB3aWRhxIcsIG5hamN6xJlzdHN6ZSB0YWdpIG96bmFjemFqxIUgasSZenlraSBwcm9ncmFtb3dhbmlhIChqYXZhLCBweXRob24sIGMjKSBsdWIgcG9wdWxhcm5lIGVsZW1lbnR5IHByb2dyYW1pc3R5Y3puZSAocmVnZXgsIGFycmF5cywganNvbikNCmBgYHtyIGVjaG8gPSBGLCBlcnJvciA9IEYsIHdhcm5pbmcgPSBGLCBtZXNzYWdlID0gRn0NCndvcmRjbG91ZCh0YWdzX2NvdW50JFRhZ05hbWUsIHRhZ3NfY291bnQkbiwgbWluLmZyZXEgPSAzNTAsIHNjYWxlPWMoNSwgLjIpLCByYW5kb20ub3JkZXIgPSBGQUxTRSkNCmBgYA0KDQoqKioNCg0KIyMjIFN1cGVydGFnaQ0KVyBjZWx1IHXFgmF0d2llbmlhIGludGVycHJldGFjamkgdGFnw7N3LCBrdMOzcnljaCBqZXN0IGHFvCBgciBucm93KHRhZ3NfY291bnQpYCB3eWtvbmFsacWbbXkgaWNoIGdydXBvd2FuaWUgZG8gInN1cGVydGFnw7N3Ii4gTWV0b2RhIGdydXBvd2FuaWEgd3lrb3J6eXN0dWplIGZha3QsIMW8ZSBkbyB3acSZa3N6b8WbY2kgcG9zdMOzdyBwcnp5cGlzYW5lIHPEhSBjbyBuYWptbmllaiAyIHRhZ2kuIFNwcmF3ZHphbXksIGt0w7NyeSB6IG5pY2ggamVzdCBwb3B1bGFybmllanN6eSBvZ8OzxYJlbSBpIGRsYSBrYcW8ZGVnbyBwb3N0YSBwcnp5cGlzdWplbXkgdGVuIG5hanBvcHVsYXJuaWVqc3p5IGpha28gc3VwZXJ0YWcgZG8gd3N6eXN0a2ljaCBwb3pvc3RhxYJ5Y2ggdGFnw7N3LiBKZcW8ZWxpIHcgd2nEmWtzem/Fm2NpIHBvc3TDs3cgeiBuYXN6ZWogYmF6eSBkYW55Y2ggZGFueSB0YWcgamVzdCBwcnp5cGlzYW55IGRvIGRhbmVnbyBzdXBlcnRhZ2EsIHRvIHV6bmFqZW15IHRlIGdydXBvd2FuaWUgemEgemFzYWRuZS4gSmXFm2xpIGplZG5hayB3eXN0xJlwdWrEhSByw7PFvG5lIHN1cGVydGFnaSBpIMW8YWRlbiBuaWUgc3Rhbm93aSB3acSZa3N6b8WbY2ksIHRvIG5pZSBwcnp5cGlzdWplbXkgxbxhZG5lZ28gc3VwZXJ0YWdhLg0KDQpQb25pxbxzemEgY2htdXJhIHByZXplbnR1amUgd3N6eXN0a2llIHN1cGVydGFnaSwga3TDs3JlIHd5dHdvcnp5bGnFm215LCBgciBucm93KHN1cGVydGFnc19jb3VudClgIHN6dHVrLiBQb2tyeXdhasSFIG9uZSBjYS4gODUlIHd5c3TEhXBpZcWEIHdzenlzdGtpY2ggdGFnw7N3IHcgcG9zdGFjaC4NCg0KYGBge3IgZWNobyA9IEYsIGVycm9yID0gRiwgd2FybmluZyA9IEYsIG1lc3NhZ2UgPSBGfQ0Kc3VwZXJ0YWdzX2NvdW50IDwtIHN1cGVydGFncyAlPiUNCiAgZ3JvdXBfYnkoU3VwZXJ0YWcpICU+JQ0KICBzdW1tYXJpc2UobiA9IHN1bShOX29icywgbmEucm0gPSBUKSkNCg0Kd29yZGNsb3VkKHN1cGVydGFnc19jb3VudCRTdXBlcnRhZywgc3VwZXJ0YWdzX2NvdW50JG4sIG1pbi5mcmVxID0gMCwgc2NhbGU9Yyg1LCAuMiksIHJhbmRvbS5vcmRlciA9IEZBTFNFKQ0KYGBgDQoNCiMjIE9waXMgYmF6eSBkYW55Y2gNCg0KIyMjIFdzenlzdGtpZSB0YWJlbGUNCmBgYHtyIGVjaG8gPSBGLCBlcnJvciA9IEYsIHdhcm5pbmcgPSBGLCBtZXNzYWdlID0gRn0NCmRiIDwtIGRiQ29ubmVjdChkYkRyaXZlcignTXlTUUwnKSwgdXNlciA9ICdqYXJvc2xhdy5ib250cnVrQGl0bS1pbm5vdmF0aW9uLW15c3FsJywgcGFzc3dvcmQgPSAncTh5ckJ5NEUzVWF4Z1lndCcsIA0KICAgICAgICAgICAgICAgICBkYm5hbWUgPSAnc3RhY2tvdmVyZmxvdycsIGhvc3QgPSAnaXRtLWlubm92YXRpb24tbXlzcWwubXlzcWwuZGF0YWJhc2UuYXp1cmUuY29tJywgcG9ydCA9IDMzMDYpDQpgYGANCg0KYGBgIHtyfQ0KZGJMaXN0VGFibGVzKGRiKQ0KYGBgDQoNCkxpc3RhIHRhYmVsIHcgYmF6aWUgKnN0YWNrb3ZlcmZsb3cgQCBpdG0taW5ub3ZhdGlvbi1teXNxbC5teXNxbC5kYXRhYmFzZS5henVyZS5jb206MzMwNioNCg0KVGFiZWxlIG5hZGFqxIVjZSBzacSZIGRvIHd5a29yenlzdGFuaWEgdG86IA0KDQoqICpCYWRnZXMqIC0gaW5mb3JtYWNqZSBvIEJhZGdlJ2FjaCB1xbx5dGtvd25pa8Ozdw0KKiAqUG9zdFRhZ3MqIC0gdGFnaSBwcnp5cGlzYW5lIGRvIHBvc3TDs3cNCiogKlBvc3RzKiAtIGxpc3RhIHB5dGHFhCBpIG9kcG93aWVkemkNCiogKlVzZXJzKiAtIGluZm9ybWFjamUgbmEgdGVtYXQgdcW8eXRrb3duaWvDs3cNCg0KV3N6eXN0a2llIDQgdGFiZWxlIHPEhSB6YXByZXplbnRvd2FuZSBwb25pxbxlai4NCg0KKioqDQoNCiMjIyBUYWJlbGEgQmFkZ2VzDQpgYGB7cn0NCmNvbG5hbWVzKGJhZGdlcykNCmBgYA0KTGlzdGEga29sdW1uIHcgdGFiZWxpICpCYWRnZXMqIChpbmZvcm1hY2plIG8gQmFkZ2UnYWNoIHXFvHl0a293bmlrw7N3KS4gVGFiZWxhIHphd2llcmEgYHIgbnJvdyhiYWRnZXMpYCByZWtvcmTDs3cgaSBgciBsZW5ndGgodW5pcXVlKGJhZGdlcyROYW1lKSlgIHVuaWthbG55Y2ggKkJhZGdlcy5OYW1lKi4NCg0KYGBge3J9DQpoZWFkKGJhZGdlcywgMTApDQpgYGANClByZXplbnRhY2phIGRhbnljaCB3IHRhYmVsaSAqQmFkZ2VzKg0KDQoqKioNCg0KIyMjIFRhYmVsYSBQb3N0VGFncw0KYGBge3J9DQpjb2xuYW1lcyhwb3N0X3RhZ3MpDQpgYGANCkxpc3RhIGtvbHVtbiB3IHRhYmVsaSAqUG9zdFRhZ3MqICh0YWdpIHByenlwaXNhbmUgZG8gcG9zdMOzdykuIFRhYmVsYSB6YXdpZXJhIGByIG5yb3cocG9zdF90YWdzKWAgcmVrb3Jkw7N3LCB3IHR5bSBgciBsZW5ndGgodW5pcXVlKHBvc3RfdGFncyRUYWdOYW1lKSlgIHVuaWthbG55Y2ggKlBvc3RUYWdzLlRhZ05hbWUqLg0KDQpgYGB7cn0NCmhlYWQocG9zdF90YWdzLCAxMCkNCmBgYA0KUHJlemVudGFjamEgZGFueWNoIHcgdGFiZWxpICpQb3N0VGFncyoNCg0KKioqDQoNCiMjIyBUYWJlbGEgUG9zdHMNCmBgYHtyfQ0KY29sbmFtZXMocG9zdHMpDQpgYGANCkxpc3RhIGtvbHVtbiB3IHRhYmVsaSAqUG9zdHMqIChsaXN0YSBweXRhxYQgaSBvZHBvd2llZHppKS4gVGFiZWxhIHphd2llcmEgYHIgbnJvdyhwb3N0cylgIHJla29yZMOzdywgdyB0eW0gYHIgbGVuZ3RoKHVuaXF1ZShwb3N0cyRQYXJlbnRJZCkpYCBweXRhxYQgaSBgciBucm93KHBvc3RzKSAtIGxlbmd0aCh1bmlxdWUocG9zdHMkUGFyZW50SWQpKWAgb2Rwb3dpZWR6aS4NCg0KYGBge3J9DQpoZWFkKHBvc3RzLCAxMCkNCmBgYA0KUHJlemVudGFjamEgZGFueWNoIHcgdGFiZWxpICpQb3N0cyoNCg0KKioqDQoNCiMjIyBUYWJlbGEgVXNlcnMNCmBgYHtyfQ0KY29sbmFtZXModXNlcnMpDQpgYGANCkxpc3RhIGtvbHVtbiB3IHRhYmVsaSAqVXNlcnMqIChpbmZvcm1hY2plIG5hIHRlbWF0IHXFvHl0a293bmlrw7N3KS4gVGFiZWxhIHphd2llcmEgYHIgbnJvdyh1c2VycylgIHJla29yZMOzdywgdyB0eW0gYHIgbGVuZ3RoKHVuaXF1ZSh1c2VycyRJZCkpYCB1bmlrYWxueWNoIFVzZXIgSUQuDQoNCmBgYHtyfQ0KaGVhZCh1c2VycywgMTApDQpgYGANClByZXplbnRhY2phIGRhbnljaCB3IHRhYmVsaSAqVXNlcnMq